import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from 合并报表 import Decimal
import cmath
########################################################################################
#作图的基本设置函数
class ChartBasic(Decimal):
    def __init__(self,remain = 2):
        Decimal.__init__(self, remain)
    #使用sns设置图表颜色
    def color(self,color):
        if color == 'set2':
            sns.set_palette("Set2", 10)
        if color =='blues':
            sns.set_palette("Blues")
        if color == 'pastel':
            sns.set_palette("pastel")
        if color == 'hls':
            sns.set_palette("hls", 7)  # 用seaborn设置色板
    #获取最大的几个值
    def head_data(self, data, head, lable_column,num_column_list, sort_index):
        data2 = data.copy()
        need_column = [lable_column] + num_column_list
        data2.sort_values(by=num_column_list[sort_index], ascending=False, inplace=True)  # 进行降序排列
        data2.reset_index(inplace=True,drop=True)
        if head != None:
            head_data = data2.loc[:head-1,need_column ]
            return head_data
        else:
            return data2
    #删除总体行
    def drop_total(self,data,lable_column,total_name):
        lable_list = list(data[lable_column].values)
        total_lable_list = []
        for lable in lable_list:
            if total_name in lable:
                total_lable_list.append(lable)
        if len(total_lable_list)==1:
            total_lable = total_lable_list[0]
            total_index = data[data[lable_column] == total_lable].index
            data2 = data.drop(total_index)
            return data2
        elif len(total_lable_list)==0:
            print('lable 中没有包含{}的值，无法确定总体行\nlables :{}'.format(total_name,lable_list))
            return data
        else:
            print('lable 中存在{}个值包含 {}，无法确定总体行\nlables :{}'.format(len(total_lable_list),total_name,lable_list))
            return data
    #获取 bar_space（bar之间的间隔） 与 bar_width
    def bar_space(self, num_column_list,dui):
        if dui != True:bar_num = len(num_column_list)
        else:bar_num = 1
        if  bar_num <= 4:
            #如果两组bar间隔0.4
            space_dict = {1:0.6,2: 0.4, 3: 0.35, 4: 0.3}
            bar_space = space_dict[bar_num]
        else:
            bar_space = 0.25
        bar_width = (1 - bar_space) /  bar_num
        return bar_space, bar_width
    #获取head_data，lable_s, bar_space, bar_width
    def get_data_param(self,data, head, lable_column, num_column_list, sort_index, total_name,drop_total,
                       dui=False,plot = 'bar'):
        data2 = data.copy()
        if lable_column == 'lable_indes':
            lable_column = data.index.name
            data2 = data.reset_index()
        if drop_total == True:  # 如果要去掉总体行
            data2 = self.drop_total(data2, lable_column, total_name)
        if head != None:  # 如果只取前几个作图，则排序进行提取
            data2 = self.head_data(data2, head, lable_column, num_column_list, sort_index)
        else:
            data2 = data2[[lable_column] + num_column_list]
        lable_s = data2[lable_column]  # 提取lable列
        if plot == 'bar':
            data2.set_index(lable_column,inplace=True,drop=True)
            bar_space, bar_width = self.bar_space(num_column_list,dui)
            return data2, lable_s, bar_space, bar_width
        elif plot == 'line':
            return data2, lable_s
    #进行标题，坐标轴lable，是否显示刻度等设置
    def title_axis(self, plt, save_name=None, title=None, xticks=True, yticks=True, axis_off=False, xlabel=None,
                   ylabel=None, legend=True, despine=True, show=False):
        if title != None: plt.title(title, fontsize=15)  # 添加标题
        if xlabel != None: plt.xlabel(xlabel)  # 添加x轴标签
        if ylabel != None: plt.ylabel(ylabel)  # 添加y轴标签
        if legend == True: plt.legend()  # 添加图例

        if axis_off == True: plt.axis('off')  # 不显示坐标轴
        if xticks == False: plt.xticks([])  # 不显示x轴刻度
        if yticks == False: plt.yticks([])  # 不显示y轴刻度
        if despine == True: sns.despine()  # 去掉上框和右框

        if save_name != None:
            plt.savefig(save_name, dpi=120, bbox_inches='tight')  # 保存到当前文件夹
        if show == True: plt.show()
    #将数值转变为百分比
    def to_percent_str(self,num,to_percent,to_pp,pp_mul100):
        decimal_num = self.remain
        if cmath.isnan(num) == True:
            return None
        elif cmath.isinf(num) == True:
            return None
        if (to_percent != True) & (to_pp != True):
            return str(num)
        elif (to_percent == True) & (to_pp == True):
            print('to_percent 与 to_pp 中只能有一个为 True')
            return str(num)
        elif to_percent == True:
            reserved_decimal = '.' + str(decimal_num) + '%'
            return  format(num, reserved_decimal)
        elif to_pp == True:
            if pp_mul100 == True:
                reserved_decimal = '.' + str(decimal_num + 2)
                num = num * 100
                return format(num, reserved_decimal) + 'pp'
            else:
                reserved_decimal = '.' + str(decimal_num)
                return format(num, reserved_decimal) + 'pp'

#作图的函数
class Chart(ChartBasic):
    def __init__(self,color='pastel',alpha=1,xticks=True,yticks=True,axis_off = False,despine = True):
        ChartBasic.__init__(self,remain = 2)
        #self.myfont = mpl.font_manager.FontProperties(fname='C:/Windows/Fonts/simhei.ttf')  # 进行字体设置，解决中文无法显示的问题,后面有中文的参数都要加上,fontproperties = myfont
        self.font = ['SimHei']
        self.alpha = alpha
        self.xticks = xticks
        self.yticks = yticks
        self.axis_off = axis_off
        self.despine = despine

        sns.set_style("white")  # 设置图片样式
        ChartBasic.color(color)#调用ChartBasic里面的color函数设置色板

    #制作柱状图
    def bar(self,data,lable_column,num_column_list,head = None,sort_index =None ,save_name = None,title = None,
            xlabel=None,ylabel=None,num_lable = True,show = False,drop_total = True,total_name = '总体',dui = False,
            to_percent=False, to_pp=False, pp_mul100=False):
        # num_lable:是否添加数据标签

        #####    提取数据    #####
        data2, lable_s, bar_space, bar_width = self.get_data_param(data,head,lable_column,num_column_list,
                                                                   sort_index,total_name,drop_total,dui,plot ='bar')
        plt.rcParams['font.sans-serif'] = self.font   # 用黑体显示中文

        #####    进行作图    #####
        # 制作堆叠图
        if dui == True:
            for index, num_column in enumerate(num_column_list):
                num_s = data2[num_column]

                if index != 0:
                    chart = plt.bar(lable_s, num_s, bottom = np.sum(data2[num_column_list[:index]], axis = 1),
                                    alpha=self.alpha, label=num_column, width=bar_width)  # bottom 设置底的高度
                else:
                    chart = plt.bar(lable_s, num_s, alpha=self.alpha, label=num_column, width=bar_width)  # 要加图例必须有lable

                if num_lable == True:#添加图例
                    if index == 0:
                        # 添加数据标签，因为很多情况下x轴并非数字，因此要用bar的位置和宽度确定x,y，无法直接用x轴的数值
                        # va(verticalalignment)：垂直对齐方式 ，参数：[ ‘center’ | ‘top’ | ‘bottom’ | ‘baseline’]
                        # he(horizontalalignment)：水平对齐方式 ，参数：[ ‘center’ | ‘right’ | ‘left’]
                        for bar, num in zip(chart, num_s):  # 添加数据标签
                            nun_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                            plt.text(bar.get_x() + bar.get_width() / 2, num / 2, nun_str, ha='center', va='center')
                    else:
                        height_s = np.sum(data2[num_column_list[:index]], axis = 1) + (num_s / 2)
                        for bar, num,hight in zip(chart, num_s,height_s):  # 添加数据标签
                            nun_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                            t = plt.text(bar.get_x() + bar.get_width() / 2, hight, nun_str, ha='center', va='center')
        # 普通柱状图
        else:
            if len(num_column_list) == 1: # 如果只有1列数据
                num_column = num_column_list[0]
                num_s = data2[num_column]
                chart = plt.bar(lable_s,num_s,alpha=self.alpha,label=num_column,width = bar_width) # 要加图例必须有lable
                if num_lable == True:
                    for bar, num in zip(chart, num_s):# 添加数据标签
                        nun_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                        plt.text(bar.get_x() + bar.get_width() / 2, num, nun_str, ha='center', va='bottom')
            else:# 如果有多列数据
                xticks_s = None
                for index,column in enumerate(num_column_list): # enumerate 对可迭代对象同时迭代出index与value
                    num_s = data2[column] # 提取数据列
                    bar_x_s = np.arange(len(lable_s))#第一个bar的x坐标
                    xticks_s = np.arange(len(lable_s)) + ((bar_width * len(num_column_list)) / 2 - (bar_width / 2))#X轴刻度坐标

                    if index == 0: # 添加第一个bar
                        plt.bar(x=bar_x_s ,height=num_s, label=column, alpha=self.alpha,width = bar_width) # color='steelblue',
                        for x,y in zip(bar_x_s ,num_s): # 添加标签
                            nun_str = self.to_percent_str(y, to_percent, to_pp, pp_mul100)
                            plt.text(x, y + 1, nun_str, ha='center', va='bottom', rotation=0)
                    else: # 添后面的bar
                        plt.bar(x=bar_x_s + (bar_width * index),
                                height=num_s, label=column, alpha=self.alpha,width=bar_width) # color='indianred',
                        for x,y in zip(bar_x_s ,num_s): # 添加标签
                            nun_str = self.to_percent_str(y, to_percent, to_pp, pp_mul100)
                            plt.text(x + (bar_width * index), y + 1, nun_str, ha='center', va='bottom', rotation=0)

                plt.xticks(ticks=xticks_s,labels=list(lable_s))# 设置x轴刻度

        #####    进行标题，坐标轴lable，是否显示刻度等设置    #####
        self.title_axis( plt, save_name = save_name,title=title, xticks=self.xticks, yticks=self.yticks, axis_off=self.axis_off,
                             xlabel=xlabel, ylabel=ylabel,despine=self.despine,show = show)
    # 制作条形图
    def barh(self, data, lable_column, num_column_list, head=None, sort_index=None, save_name=None, title=None,
                xlabel=None, ylabel=None, num_lable=True, show=False, drop_total=True, total_name='总体', dui=False,
             to_percent=False, to_pp=False, pp_mul100=False):
        #####    提取数据    #####
        data2, lable_s, bar_space, bar_height = self.get_data_param(data, head, lable_column, num_column_list,
                                                                   sort_index, total_name, drop_total, dui, plot='bar')
        plt.rcParams['font.sans-serif'] = self.font  # 用黑体显示中文

        #####    进行作图    #####
        # 制作堆叠条形图
        if dui == True:
            for index, num_column in enumerate(num_column_list):
                num_s = data2[num_column]

                if index != 0:
                    chart = plt.barh(lable_s, num_s, left=np.sum(data2[num_column_list[:index]], axis=1),
                                    alpha=self.alpha, label=num_column, height=bar_height)  # left 设置底的x坐标
                else:
                    chart = plt.barh(lable_s, num_s, alpha=self.alpha, label=num_column, height=bar_height)  # 要加图例必须有lable

                if num_lable == True:#如果要添加数据标签
                    if index == 0:
                        # 添加数据标签，因为很多情况下x轴并非数字，因此要用bar的位置和宽度确定x,y，无法直接用x轴的数值
                        # va(verticalalignment)：垂直对齐方式 ，参数：[ ‘center’ | ‘top’ | ‘bottom’ | ‘baseline’]
                        # he(horizontalalignment)：水平对齐方式 ，参数：[ ‘center’ | ‘right’ | ‘left’]
                        for y, num in zip(lable_s, num_s):  # 添加数据标签
                            num_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                            plt.text(num / 2, y,  num_str, ha='center', va='center')
                    else:
                        x_s = np.sum(data2[num_column_list[:index]], axis=1) + (num_s / 2)
                        for y, num, x in zip(lable_s, num_s, x_s):  # 添加数据标签
                            num_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                            t = plt.text(x, y,  num_str, ha='center', va='center')
        # 普通条形图
        else:
            if len(num_column_list) == 1:  # 如果只有1列数据
                num_column = num_column_list[0]
                num_s = data2[num_column]
                chart = plt.barh(lable_s, num_s, alpha=self.alpha, label=num_column, height=bar_height)  # 要加图例必须有lable
                if num_lable == True:
                    for bar, num in zip(chart, num_s):  # 添加数据标签
                        num_str = self.to_percent_str(num, to_percent, to_pp, pp_mul100)
                        plt.text( num,bar.get_y() + bar.get_height() / 2, num_str, ha='left', va='center')
            else:  # 如果有多列数据
                yticks_s = None
                for index, column in enumerate(num_column_list):  # enumerate 对可迭代对象同时迭代出index与value
                    num_s = data2[column]  # 提取数据列
                    bar_x_s = np.arange(len(lable_s))  # 第一个bar的x坐标
                    yticks_s = np.arange(len(lable_s)) - \
                               ((bar_height * len(num_column_list)) / 2 - (bar_height / 2))  # X轴刻度坐标

                    if index == 0:  # 添加第一个bar
                        plt.barh(y=bar_x_s, width=num_s, label=column, alpha=self.alpha, height=bar_height)  # color='steelblue',
                        for x, y in zip(bar_x_s, num_s):  # 添加标签
                            num_str = self.to_percent_str(y, to_percent, to_pp, pp_mul100)
                            plt.text(y , x, num_str, ha='left', va='center', rotation=0)
                    else:  # 添后面的bar
                        plt.barh(y=bar_x_s - (bar_height * index),
                                 width=num_s, label=column, alpha=self.alpha, height=bar_height)  # color='indianred',
                        for x, y in zip(bar_x_s, num_s):  # 添加标签
                            num_str = self.to_percent_str(y, to_percent, to_pp, pp_mul100)
                            plt.text(y , x - (bar_height * index), num_str, ha='left', va='center', rotation=0)

                plt.yticks(ticks=yticks_s, labels=list(lable_s))  # 设置x轴刻度

        #####    进行标题，坐标轴lable，是否显示刻度等设置    #####
        self.title_axis(plt, save_name=save_name, title=title, xticks=self.xticks, yticks=self.yticks,
                        axis_off=self.axis_off,
                        xlabel=xlabel, ylabel=ylabel, despine=self.despine, show=show)
    def percent_dui(self, data, lable_column, num_column_list, direction='bar', head=None, sort_index=None, save_name=None,
                    title=None,xlabel=None, ylabel=None, num_lable=True, show=False, drop_total=True, total_name='总体'):
        '''
        :param data:frame 必传
        :param lable_column:str lable列列名,若index为lable，则填写'lable_indes'
        :param num_column_list: list 数据列列名list 必传
        :param direction: str 选择方向'bar'柱形图，'barh'条形图，default'bar'
        :param head: int 保留最大的几个值，default None 使用所有数据；如果传入head，short_index必传
        :param sort_index: int 对数据进行降序排序的index（对应num_column_list）
        :param save_name: str 图片保存的名称，default None，不保存
        :param title: str 标题名称，default None，无标题
        :param xlabel: str x轴标签，default None，无lable
        :param ylabel: str y轴标签，default None，无lable
        :param num_lable: boll， default True 是否添加数据标签
        :param show: boll，default False 是否展示图片（展示了就得先关了程序才能继续运行）
        :param drop_total: boll，default True 删除总体列
        :param total_name: str，default '总体'，在lable列中寻找包含total_name的lable作为总体列
        :return:
        '''
        data2 = data.copy() # 复制一份数据
        data2['total'] = np.sum(data2[num_column_list], axis = 1) #加一列计算总计值
        # 分别计算每列所占的比重
        for column in num_column_list:
            data2[column + '_1'] = data2[column]/data2['total']
        data2.drop(num_column_list, axis=1, inplace=True) # 删除原有的列
        data2.drop('total', axis=1, inplace=True)# 删除总体列

        for column in num_column_list:# 对计算占比的列更改列名
            data2.rename(columns={column + '_1':column}, inplace = True)

        if direction == 'bar':#进行柱图制作
            self.bar(data2, lable_column, num_column_list, head=head, sort_index=sort_index, save_name=save_name,
                     title=title,xlabel=xlabel, ylabel=ylabel, num_lable=num_lable, show=show, drop_total=drop_total,
                     total_name=total_name, dui=True,to_percent=True)
        if direction == 'barh':#进行条形图制作
            self.barh(data2, lable_column, num_column_list, head=head, sort_index=sort_index, save_name=save_name,
                     title=title,xlabel=xlabel, ylabel=ylabel, num_lable=num_lable, show=show, drop_total=drop_total,
                     total_name=total_name, dui=True,to_percent=True)
    #制作线图
    def line(self,data,lable_column,num_column_list,head = None,sort_index =None ,save_name = None,title = None,
            xlabel=None,ylabel=None,num_lable = True,show = False,drop_total = True,total_name = '总体',
             to_percent=False, to_pp=False, pp_mul100=False):
        #####    提取数据    #####
        data2, lable_s= self.get_data_param(data, head, lable_column, num_column_list,
                                            sort_index, total_name, drop_total, plot='line')
        plt.rcParams['font.sans-serif'] = self.font  # 用黑体显示中文

        #####    进行作图    #####
        xticks_s = np.arange(len(lable_s))
        for index, num_column in enumerate(num_column_list):
            num_s = data2[num_column]  # 提取数据列
            plt.plot(xticks_s, num_s,alpha=self.alpha, marker='.', ms=10, label=num_column)
            if num_lable == True:
                for x, y in zip(xticks_s, num_s):  # 添加标签
                    plt.text(x, y + 1, str(y), ha='center', va='bottom', rotation=0)

        plt.xticks(ticks=xticks_s, labels=list(lable_s))  # 设置x轴刻度

        #####    进行标题，坐标轴lable，是否显示刻度等设置    #####
        self.title_axis(plt, save_name=save_name, title=title, xticks=self.xticks, yticks=self.yticks,
                        axis_off=self.axis_off,xlabel=xlabel, ylabel=ylabel, despine=self.despine, show=show)
    #制作两轴柱线图
    def bar_line(self,data,lable_column,num_bar_column_list,num_line_column_list,head = None,sort_index =None ,
                 save_name = None,title = None,xlabel=None,ylabel=None,num_lable = True,show = False,
                 drop_total = True,total_name = '总体'):
        pass

    # 制作两轴柱状图

    # 制作饼图
    def pie(self):
        pass
    # 制作气泡图
    def color(self):
        pass

if __name__ == '__main__':
    file_neme = '1_1投诉区域指标表_18年-19年.xlsx'
    frame = pd.read_excel(file_neme)
    lable_s = frame['区域A']
    num_s = frame['19年']

    chart = Chart(alpha=0.5)
    #chart.bar(frame,'区域A',['18年','19年'],show=True,save_name='测试.png',head = 5,sort_index =0,dui = True)
    #chart.bar(frame, '区域A', ['18年', '19年'], show=True, save_name='测试.png', head=5, sort_index=0)
    #chart.bar(frame, '区域A', ['19年'], show=True, save_name='测试.png', head=5, sort_index=0)

    #chart.barh(frame, '区域A', ['19年'], show=True, save_name='测试.png', head=5, sort_index=0)
    #chart.barh(frame, '区域A', ['18年','19年'], show=True, save_name='测试.png', head=5, sort_index=0)
    #chart.barh(frame, '区域A', ['18年', '19年'], show=True, save_name='测试.png', head=5, sort_index=0, dui=True)

    chart.percent_dui(frame, '区域A', ['18年','19年'], direction = 'barh',show=True, save_name='测试.png', head=5, sort_index=0)

    #chart.line(frame, '区域A', ['18年','19年'], show=True, save_name='测试.png', head=5, sort_index=0)